/*
 * 文件名：Apns.java
 * 版权：
 * 描述：TODO
 * 修改人：zuo liubo
 * 修改时间：2013-4-8 下午3:29:51
 */
package com.scpii.api.service.push.apns;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import javapns.Push;
import javapns.devices.exceptions.InvalidDeviceTokenFormatException;
import javapns.notification.PayloadPerDevice;
import javapns.notification.PushedNotification;
import javapns.notification.PushedNotifications;

import javax.annotation.Resource;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Component;

import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.Session;
import com.scpii.api.common.Constants;
import com.scpii.api.common.EcConstants;
import com.scpii.api.domain.push.Message;
import com.scpii.api.domain.push.UserMessageDevices;
import com.scpii.api.persistence.push.MessageMapper;
import com.scpii.api.persistence.push.UserMessageDevicesMapper;
import com.scpii.api.persistence.push.UserMessagesMapper;
import com.scpii.api.service.push.PushService;

/**
 * @description 向APNS发送推送信息
 * @author zuo liubo
 * @date 2013-4-8
 */
@Component("apns")
public class Apns {
	private Log logger = LogFactory.getLog(Apns.class);
	@Resource
	private UserMessageDevicesMapper userMessageDevicesMapper;
	@Resource
	private MessageMapper messageMapper;
	@Resource
	private PushService pushService;
	@Resource
	private UserMessagesMapper userMessagesMapper;

	private ExecutorService exec;

	public Apns() {
		exec = Executors.newFixedThreadPool(5);
	}

	public void sendPushInfo() throws Exception {
		List<UserMessageDevices> umdList = userMessageDevicesMapper.selectApp4Push();
		int appCount = umdList.size();
		logger.info("应用个数：" + appCount);
		final CountDownLatch latch = new CountDownLatch(appCount);
		for (UserMessageDevices umd : umdList) {
			final UserMessageDevices _umd = umd;
			exec.submit(new Callable<Void>() {
				@Override
				public Void call() {
					try {
						// 1、获取message的信息
						Message msg = messageMapper.selectByPrimaryKey(_umd.getMessageId());
						// 2、封装向APNS服务器推送的消息体
						String message = ApnsUtil.encapsulationMsg(msg);
						// 3、获取tokens 和 P12文件
						Map<String, Object> map = getTokensAndP12Path(_umd);
						// 4、发送推送消息
						sendMessage(message, map, _umd);
					} catch (Exception e) {
						logger.error(e);
					} finally {
						latch.countDown();
					}
					return null;
				}
			});
		}
	}

	/**
	 * 发送推送消息
	 * 
	 * @param message
	 *            推送的消息体
	 * @param map
	 *            包含iosToken 和 p12文件名称以及路径和秘钥
	 * @param umd
	 */
	private void sendMessage(String message, Map<String, Object> map, UserMessageDevices umd) {
		InputStream p12 = null;
		Session sshSession = null;
		Channel channel = null;
		ChannelSftp sftp = null;
		try {
			logger.info("推送参数：message=" + message + "|||map=" + map + "|||userMessage=" + umd);
			String host = Constants.PUSH_FTP_HOST;
			int port = Integer.parseInt(Constants.PUSH_FTP_PORT);
			String username = Constants.PUSH_USERNAME;
			String password = Constants.PUSH_PASSWORD;
			logger.info("host:" + host + "   port:" + port + "   username:" + username + "   password:" + password);
			JSch jsch = new JSch();
			jsch.getSession(username, host, port);
			sshSession = jsch.getSession(username, host, port);
			sshSession.setPassword(password);
			Properties sshConfig = new Properties();
			sshConfig.put("StrictHostKeyChecking", "no");
			sshSession.setConfig(sshConfig);
			sshSession.connect();
			channel = sshSession.openChannel("sftp");
			channel.connect();
			sftp = (ChannelSftp) channel;
			logger.info("SFTP连接主机" + host + "成功");

			String secret = (String) map.get("secret");
			String p12Path = (String) map.get("p12Path");
			String dir = p12Path.substring(0, p12Path.lastIndexOf("/"));
			String file = p12Path.substring(p12Path.lastIndexOf("/") + 1);
			sftp.cd(dir);
			p12 = sftp.get(file);

			@SuppressWarnings("unchecked")
			List<String> devices = (List<String>) map.get("tokens");
			// 更新数据库
			if (devices.size() > 0) {
				pushService.updateMessageStatusByIosToken(umd, devices);
				pushService.messageStatusToHasPush(umd, devices);

				int numberOfThreads = devices.size() / 100 + 1;

				PushedNotifications notifications = Push.payloads(p12, secret, false, numberOfThreads,
						getMsgPerDevice(message, umd.getAppId(), devices));
				// 打印一些基本信息
				List<PushedNotification> failedNotifications = PushedNotification
						.findFailedNotifications(notifications);
				List<PushedNotification> successfulNotifications = PushedNotification
						.findSuccessfulNotifications(notifications);
				int failed = failedNotifications.size();
				int successful = successfulNotifications.size();
				logger.info("成功推送：" + successful + " 失败推送：" + failed);
			}
		} catch (Exception e) {
			e.getStackTrace();
			logger.error("IOS推送出错：" + e.getMessage());
			logger.info(e.getMessage());
		} finally {
			if (sftp != null)
				sftp.disconnect();
			sftp.exit();
			if (channel != null)
				channel.disconnect();
			if (sshSession != null)
				sshSession.disconnect();
			if (p12 != null)
				try {
					p12.close();
				} catch (IOException e) {
				}
		}

	}

	/**
	 * @param umd
	 * @return
	 */
	private Map<String, Object> getTokensAndP12Path(UserMessageDevices umd) {
		Map<String, Object> map = new HashMap<String, Object>();
		List<String> tokens = userMessageDevicesMapper.selectIOSTokens(umd);
		logger.info("推送的设备个数：" + tokens.size());
		map.put("tokens", tokens);
		map.put("p12Path", Constants.PUSH_DIRECTORY + "/" + umd.getAppId() + ".p12");
		map.put("secret", EcConstants.P12SECRET);
		return map;
	}

	/**
	 * 根据message获取Payload
	 * 
	 * @param message
	 *            消息信息
	 * @param appId
	 *            应用ID
	 * @param devices
	 *            IOS TOKEN
	 * @return
	 */
	private List<PayloadPerDevice> getMsgPerDevice(String message, Long appId, List<String> devices) {
		List<Map<String, Object>> list = userMessagesMapper.selectNotReadCount(appId, devices);
		List<PayloadPerDevice> payloadPerDevices = new ArrayList<PayloadPerDevice>();
		for (Map<String, Object> m : list) {
			int badge = Integer.parseInt(m.get("NUM").toString());
			try {
				PayloadPerDevice perDevice = new PayloadPerDevice(ApnsUtil.getPayload(message, badge), m
						.get("IOSTOKEN").toString());
				payloadPerDevices.add(perDevice);
			} catch (InvalidDeviceTokenFormatException e) {
				e.printStackTrace();
			}
		}
		return payloadPerDevices;
	}
}
